/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package questionbook.store;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.GraphQuery;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFWriter;
import org.openrdf.rio.Rio;
import org.openrdf.sail.inferencer.fc.ForwardChainingRDFSInferencer;
import org.openrdf.sail.memory.MemoryStore;
import org.openrdf.sail.nativerdf.NativeStore;

/**
 * The RdfStore Class is a Wrapper for the Sesame Functionality
 * for more Information look at http://www.openrdf.org/
 * 
 * @author stefanstift
 */
public class RdfStore {

    private Repository repository = null;



    /**
     * Create a RdfStore Instance without Sesame RDFS-inferrencing
     */
    public RdfStore() {
        this(false);
    }

    /**
     * Create a RdfStore Instance
     *
     * @param inferencing defines if internal Sesame RDFS-Inferencing should be used
     */
    public RdfStore(boolean inferencing) {
        try {
            if(inferencing)
            {
                this.repository = new SailRepository(
                                new ForwardChainingRDFSInferencer(
                                new MemoryStore()));
            }
            else{
                this.repository = new SailRepository(new MemoryStore());
            }
            this.repository.initialize();
        }
        catch(RepositoryException re){
            re.printStackTrace();
        }
    }

    /**
     * Adds a Namespace into the Store with the specified prefix,
     * this prefix is ready to use in the whole store after the definition
     *
     * @param prefix
     * @param url
     */
    public void addNamespace(String prefix, String url)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                conn.setNamespace(prefix, url);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }        
    }

    /**
     * Creates a Literal-Object from a String, this is needed for the internal Sesame
     * RDF-Model, it is also possible to specify the Type of the String
     *
     * @param s
     * @param typeUri
     * @return
     */
    public Literal getLiteral(String s, URI typeUri)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                if(typeUri == null)
                {
                    return vf.createLiteral(s);
                }
                else
                {
                    return vf.createLiteral(s, typeUri);
                }
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Creates a Literal-Object from a Long-Value, this is needed for the internal Sesame
     * RDF-Model
     *
     * @param l
     * @return
     */
    public Literal getLiteral(Long l)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                return vf.createLiteral(l);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Creates a Literal-Object from a String, this is needed for the internal Sesame
     * RDF-Model
     * 
     * @param s
     * @return
     */
    public Literal getLiteral(String s)
    {
        return this.getLiteral(s, null);
    }

    /**
     *
     * @param uri
     * @return the URI-Representation of the given String
     */
    public URI getURIref(String uri)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                return vf.createURI(uri);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @return a BNode with a generated internal ID
     */
    public BNode getBNode()
    {
        try
        {
            RepositoryConnection conn =  this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                return vf.createBNode();
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
        
    }

    /**
     * Adds a Triple to the Store
     *
     * @param subject
     * @param predicate
     * @param object
     */
    public void add(URI subject, URI predicate, Value object)
    {
        try
        {
            RepositoryConnection conn =  this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                Statement st = vf.createStatement(subject, predicate, object);
                conn.add(st);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("Adding Triple raised an exception");
            e.printStackTrace();
        }
    }

    /**
     * Adds a Triple witha  BNode-Subject to the Store
     *
     * @param subject
     * @param predicate
     * @param object
     */
    public void add(BNode subject, URI predicate, Value object)
    {
        try
        {
            RepositoryConnection conn =  this.repository.getConnection();
            try
            {
                ValueFactory vf = conn.getValueFactory();
                Statement st = vf.createStatement(subject, predicate, object);
                conn.add(st);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("add Tripple raised an exception");
            e.printStackTrace();
        }
    }

    /**
     * Adds a String that holds serialized RDF-Information to the Store
     *
     * @param rdfString
     * @param format the RDF-Format (e.g. N3, RDF/XML)
     */
    public void addString(String rdfString, RDFFormat format)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                StringReader reader = new StringReader(rdfString);
                conn.add(reader, "", format);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("add Rdf String raised an exception");
            e.printStackTrace();
        }
    }

    /**
     * Adds a RDF-File to the Store
     *
     * @param filePath
     * @param format  the RDF-Format (e.g. N3, RDF/XML)
     */
    public void addFile(String filePath, RDFFormat format)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                conn.add(new File(filePath), "", format);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    /**
     * Adds a online RDF-Source to the Store
     *
     * @param urlString
     * @param format  the RDF-Format (e.g. N3, RDF/XML)
     */
    public void addURI(String urlString, RDFFormat format)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                URL url = new URL(urlString);
                URLConnection urlConn = url.openConnection();
                urlConn.addRequestProperty("accept", format.getDefaultMIMEType());
                InputStream input = urlConn.getInputStream();
                conn.add(input, urlString, format);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("add Rdf URI raised an exception");
            e.printStackTrace();
        }
    }

    /**
     * Adds a URI to the Store
     *
     * @param uriString
     */
    public void addURI(String uriString)
    {
        this.addURI(uriString, RDFFormat.RDFXML);
    }

    /**
     * Serializes the actual Store to the specified Outputstream
     *
     * @param out
     * @param format  the RDF-Format (e.g. N3, RDF/XML)
     */
    public void dumpRDF(OutputStream out, RDFFormat format)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
//              RDFXMLPrettyWriter writer = new RDFXMLPrettyWriter(out);

                RDFWriter writer = Rio.createWriter(format, out);
                conn.export(writer);
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("write Rdf Output raised an exception");
            e.printStackTrace();
        }
    }

    /**
     * Removes a Triple from the Store, null as wildcard is possible
     *
     * @param subject
     * @param predicate
     * @param object
     */
    public void removeTuplePattern(URI subject, URI predicate, Value object)
    {
          try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                 conn.remove(subject, predicate, object);

            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("tupple Patter query raised an exception");
            e.printStackTrace();
        }
    }


   /**
    * Query the Store using Subject, Predicate and Object, null as wildcard is
    * possible
    *
    * @param subject
    * @param predicate
    * @param object
    * @return aList of all Statements that match the specified Pattern
    */
    public List tuplePattern(URI subject, URI predicate, Value object)
    {
        try
        {
            RepositoryConnection conn = this.repository.getConnection();
            try
            {
                RepositoryResult result = conn.getStatements(subject, predicate, object, true);
                ArrayList resultList = new ArrayList();
                while(result.hasNext())
                {
                    resultList.add(result.next());
                }

                return resultList;
            }
            finally
            {
                conn.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("tupple Patter query raised an exception");
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Preforms a SPARQL Query and returns the result as RDF-Graph
     *
     * @param query
     * @param format  the RDF-Format (e.g. N3, RDF/XML)
     * @return the RDF-String representing the Result-Triples
     */
    public String runSPARQL(String query, RDFFormat format) {
        try {
            RepositoryConnection conn = this.repository.getConnection();
            try {
                GraphQuery graph = conn.prepareGraphQuery(QueryLanguage.SPARQL, query);
                StringWriter writer = new StringWriter();
                RDFWriter rdfWriter = Rio.createWriter(format, writer);
                graph.evaluate(rdfWriter);
                return writer.toString();
            }
            finally {
                conn.close();
            }
        }
        catch(Exception e) {
            System.out.println("SPARQL CONSTRUCT/DESCRIBE query raised an exception");
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Performs a SPARQL-Query and returns the defined Bindings
     *
     * @param query
     * @return a List of all found Bindings for the Query
     */
    public List runSPARQL(String query) {
        try {
            RepositoryConnection conn = this.repository.getConnection();
            try {
                TupleQuery tuple = conn.prepareTupleQuery(QueryLanguage.SPARQL,
                                                          query);
                TupleQueryResult result = tuple.evaluate();
                ArrayList resultList = new ArrayList();

                while(result.hasNext()) {
                    BindingSet bindingSet = result.next();
                    Set names = bindingSet.getBindingNames();
                    Map map = new HashMap();
                    for(Object key : names) {
                        map.put((String) key, bindingSet.getValue((String) key));
                    }
                    resultList.add(map);
                }
                return resultList;
            }
            finally {
                conn.close();
            }
        }
        catch(Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    


}
